Metasploit CTF 2020: RMF / Ace of Diamonds Write-Up

Heya, here's a write-up for the Repository Manager Frontend web application from the 2020 Metasploit CTF. Although it made for a busy weekend juggling it with other things in life, it was an awesome way to do team building with friends. The cherry on top is that we managed to finish in 3rd place! Congrats go to pepega and excusemewtf for winning 1st and 2nd, and cheers to the Metasploit team for all the hard work that went into the challenges and infrastructure.

This write-up is structured in a way to go through the thought process, but if you'd rather cut to the chase, here it is (spoiler alert...): The frontend was for a version of Nexus which was vulnerable to command execution. It was exploitable using JSON injection in the "notes" argument when updating a capability.


The challenge started on the target box on port 880, and we're presented a Ruby on Rails app which allows us to view/edit repos and manage capabilities.

While taking a cursory glance through the repository pages, I was hoping that we could load up a repo that is unlisted, perhaps one containing the flag, and then be able to access its contents through the app. Alas, it was not fruitful as there didn't appear to be a way to read file data.

The capabilities pages ostensibly allowed users to read and update notes. We would only get a status message when updating the note for the Yum capability, whereas other capabilities would return a blank status when updated.

Taking a step back, we mapped out the visible attack surface that the web app allowed us to manipulate, assuming that the next steps would be at this layer of abstraction:

- POST /groups
  - update_group     => Update a group: change the name or add a repository to it.
    - group_id
    - name
    - add_repo
  - repo             => Create a repository with an id, name, and provider.
    - id
    - name
    - provider
- POST /capabilities
  - update_capa      => Update the notes associated with a capabiility.
    - capa_id
    - notes


Playing around with the list of providers one can choose from when creating a repo, my teammate h4ckNinja observed that changing a provider from rubygems-hosted to rubygems-proxy would return an escaped HTML page raising a 500 error.

The response made it more obvious that Sonatype Nexus was being used on the backend to manage the repositories. This was interesting because maybe there was the potential for SSRF... what if we could query arbitrary APIs on nexushost:8081 or talk to other containers?

What was also interesting was that version 2.14.14-01 was being used. With some searches we found that this version was vulnerable to authenticated RCE with OS command injection. The injection vector could exploitable by updating the createrepoPath or modifyrepoPath properties of the Yum capability with malicious commands. After updating, those property values are executed with a --version argument. For example, if calc.exe was provided as the "malicious" command, then calc.exe --version would be executed. I'm simplifying a little here, but there are more details in both HackerOne reports: #654888, #688270.


When it came to exploitation, we didn't have to worry about the authentication part because that's assumed when the frontend communicates to the backend. Being able to modify those specific properties proved to be more challenging. We initially assumed that we could add those properties into the update_capa request (shown below) with the expectation that the status message in the response would update with the output for our commands.

It was getting into the wee hours of the night while we tried out many different variations of the request above, but it just wasn't working.

As we progressed, it began to make sense why the idea of exploiting a mass assignment vulnerability wouldn't have worked. If this was indeed a mass assignment vulnerability, we should have been able to modify the capability object with requests like the one above. Looking further into the Nexus API, it turns out that an id attribute is used to reference the capability, and there is no capa_id attribute. So we were probably still interacting with the frontend (which ignored our additional input) instead of the backend.

Out of ideas and enough sleep, I started smashing keys into the update note field and saw something interesting. See below for the underwhelming re-enactment:

What? An error message? Unexpected character '#'? What was right before that... a double-quote? And it's expecting a comma to come after... to separate Object entries with a capital "O", like JSON objects? Wait, JSON objects... are they concatenating the user-input into stringified JSON and sending that to the backend? Why would anyone do that?? How often does that come up in real-world modern web apps? Oh, this is a CTF.

After that realization, it was clear that the payload needed to go into the notes argument. Below you can see the scenarios for expected and unexpected behaviour with string concatenation:

The minimal test payload looks like this: MyNote1", "properties": [{"key": "createrepoPath", "value": "/bin/sh -c id || /createrepo"}], "notes": "MyNote2"}. Previous attempts did not have the "} suffix, which would still result in the note being updated to "MyNote2" but not setting or executing the new createrepoPath value. As we knew the Nexus version was 2.14.14-01, we knew that simply providing the command would not work, because this instance is patched for CVE-2019-5475 but not for CVE-2019-15588.

Next, we replaced the baseline payload with one for a reverse shell, searched around the system for the flag, and got its MD5.

Here's the flag card for Ace of Diamonds:

That's all! Thanks for reading and I hope you enjoyed the write-up if you made it this far.

Greetz to Team Exit and Metasploit CTF crew.